// import * as FileSaver from 'file-saver'
import {saveAs} from 'file-saver'
import * as ExcelJS from 'exceljs'
// export const FileSaverJs = FileSaver

// https://github.com/exceljs/exceljs/blob/master/README_zh.md#%E5%B7%A5%E4%BD%9C%E8%A1%A8%E5%B1%9E%E6%80%A7
export class MyExcel {

  constructor() {
    this.workbook = new ExcelJS.Workbook()
    this.workbook.creator = 'xhyk'
    this.workbook.created = new Date()
    this.workbook.modified = new Date()
    // 在加载时强制工作簿计算属性
    this.workbook.calcProperties.fullCalcOnLoad = true;
    this.workSheetList = []
    this.defaultSheetStyle = {
      properties: {
        // 标签的颜色
        tabColor: {argb: 'FFFF0000'},
        // 工作表列大纲级别
        outlineLevelCol: 0,
        // 工作表行大纲级别
        outlineLevelRow: 0,
        // 默认行高
        defaultRowHeight: 35,
        // 默认列宽
        defaultColWidth: 20,
        // TBD
        dyDescent: 55
      }
    }

    // 当前操作表
    this.nowSheet = undefined


    // region 样式说明

    // region 填充字段：fill。如：cell.fill = {type:'pattern',pattern:'solid',fgColor:{argb:'FFFF0000'}}
    this.defaultFill = {
      // 指定图案前景色，即单元格中看到的背景色。默认为黑色。
      fgColor: {
        argb: 'FF0000FF'
      },
      // 指定图案背景色。默认为白色。
      bgColor: {},
      // 对于“角度”渐变，指定渐变的方向。0 是从左到右。值从 1-359 顺时针旋转方向
      degrees: 0,
      // 对于“路径”渐变。指定路径起点的相对坐标。“左”和“顶”值的范围是 0 到 1
      center: {left: 0.5, top: 0.5},
      // 指定渐变颜色序列。是包含位置和颜色（从位置 0 开始到位置 1 结束）的对象的数组。中间位置可用于指定路径上的其他颜色
      stops: [
        {position: 0, color: {argb: 'FFFF0000'}},
        {position: 1, color: {argb: 'FF00FF00'}}
      ],
      // 指定渐变类型。['angle'，'path'] 之一。path时center值有效果；angle时degrees值有效果
      gradient: '',

      // 指定模式类型，必填。默认为solid。
      pattern: 'solid',
      // 指定图案类型，必填。默认为pattern，gradient渐变填充。
      type: 'pattern',

      /* type为pattern时。pattern：有一下值
      * none： 无图案。
      * solid： 纯色图案。
      * darkGray：粗六方形网格
      * mediumGray：细六方形网格
      * lightGray：砖形填充
      * gray125：密集点填充
      * gray0625：疏点填充
      * darkHorizontal：横网格
      * darkVertical：竖网格
      * darkDown：左上右下斜网格
      * darkUp：左下右上斜网格
      * darkTrellis：长方形网格
      * lightHorizontal：细横网格
      * lightVertical：细竖网格
      * lightDown：细左上右下斜网格
      * lightUp：细左下右上斜网格
      * lightGrid：方形网格
      * lightTrellis：X形网格
      * */

    }
    // endregion

    // region 字体字段：font。如：cell.font = {name:'Arial',size:11,color:{argb:'FFFF0000'}}
    this.defaultFont = {
      // 字体名称。如：'Arial', 'Calibri', etc.
      name: '',
      // 备用字体家族。整数值。如：1 - Serif, 2 - Sans Serif, 3 - Mono, Others - unknown
      family: 1,
      // 字体方案。'minor', 'major', 'none'
      scheme: '',
      // 字体字符集。整数值。1, 2等
      charset: '',
      // 字体大小。整数值。9, 10, 12, 16等
      size: '',
      // 颜色描述，一个包含 ARGB 值的对象。{ argb: 'FFFF0000'}
      color: '',
      // 字体 粗细
      bold: false,
      // 字体 倾斜
      italic: false,
      // 字体 下划线 样式。true, false, 'none', 'single', 'double', 'singleAccounting', 'doubleAccounting'
      underline: false,
      // 字体 删除线
      strike: false,
      // 字体轮廓
      outline: false,
      // 垂直对齐。'superscript', 'subscript'
      vertAlign: '',
    }
    // endregion

    // region 富文本字段(注意是内容字段value里面的)。如：cell.value.richText = [{font:{name:'Arial',size:11,color:{argb:'FFFF0000'}}},'hello world']
    this.defaultRichText = [
      // 把text里面的字体内容，设置成font的字体样式。换行用\n
      {font: {}, text: ''}
    ]
    // endregion

    // region 单元格对齐方式字段：alignment。
    this.defaultAlignment = {
      // 垂直对齐方式。值：top：上；middle：中；bottom：下；distributed：两端对齐；justify：两端对齐
      vertical: 'middle',
      // 水平对齐方式。值：left：左；center：中；right：右；justify：两端对齐；fill：铺满；centerContinuous；distributed
      horizontal: 'center',
      // 文本换行
      wrapText: true,
      // 缩进
      indent: 0,
      // 阅读顺序。rtl：从右到左；ltr：从左到右
      readingOrder: '',
      // 旋转角度。整数值，0-90之间，或文本值：'vertical'
      textRotation: 0,
    }
    // endregion

    // region 边框字段：border。如：cell.border = {top:{style:'thin',color:{argb:'FFFF0000'}},left:{style:'thin'}}
    this.defaultBorder = {
      // 顶边
      top: {
        // 类型
        style: 'thin',
        // 颜色
        color: {argb: 'FFFF0000'},
        /* 边类型
        * thin：细实线
        * dotted：虚线
        * dashDot：点划线
        * hair：细虚线
        * dashDotDot：双点划线
        * slantDashDot：倾斜点划线
        * mediumDashed：粗划线
        * mediumDashDotDot：粗双点划线
        * mediumDashDot：粗点划线
        * medium：粗实线
        * thick：很粗实线
        * double：双细实线
        * none：无
        *  */
      },
      // 左边
      left: {},
      // 底边
      bottom: {},
      // 右边
      right: {},
    }
    // endregion

    // endregion

  }

  // region 工作表。workbook里面的_worksheet列表有空属性，不好处理数据

  // region 方法

  // 添加
  addWorksheet(sheetName, sheetStyle = {}) {
    if (this.workSheetList.find(row => row.sheetName == sheetName)) {
      console.log(`新增表格失败：工作表【${sheetName}】已存在`);
      return;
    }
    const newStyle = Object.assign(JSON.parse(JSON.stringify(this.defaultSheetStyle)), sheetStyle)
    const newSheet = this.workbook.addWorksheet(sheetName, newStyle)
    this.workSheetList.push({sheetName, sheet: newSheet, sheetId: newSheet.id})
    this.nowSheet = newSheet
    return newSheet
  }

  // 根据名称或id去删除。isLike：如果是名称时是否模糊匹配
  delWorksheet(nameOrSheetId, isLike = false) {
    const newList = this.workSheetList.filter(sheetItem => {
      if (sheetItem.sheetName == nameOrSheetId || (isLike && sheetItem.sheetName.includes(nameOrSheetId))) {
        this.workbook.removeWorksheet(sheetItem.sheet.id)
        return false
      } else if (sheetItem.sheet.id == nameOrSheetId) {
        this.workbook.removeWorksheet(sheetItem.sheet.id)
        return false
      }
      return true
    })
    this.workSheetList = newList
  }

  // 根据表名或id获取表
  getWorksheet(nameOrSheetId) {
    const result = this.workSheetList.find(sheetItem => {
      if (sheetItem.sheetName == nameOrSheetId) {
        return sheetItem.sheetName
      } else if (sheetItem.sheet.id == nameOrSheetId) {
        return sheetItem.sheetName
      }
    })
    return result?.sheet
  }

  // 根据序号获取表。从1开始
  getWorksheetByIndex(sheetIndex) {
    if (sheetIndex > this.workSheetList.length - 1) {
      sheetIndex = this.workSheetList.length - 1
    }
    return this.workSheetList[sheetIndex]?.sheet
  }

  // 设置为当前操作表
  setWorksheet(nameOrSheetId) {
    this.nowSheet = this.getWorksheet(nameOrSheetId)
  }

  // 通过序号设置为当前操作表
  setWorksheetByIndex(sheetIndex) {
    this.nowSheet = this.getWorksheetByIndex(sheetIndex)
  }

  // 显示或隐藏表。可见：【true/false】/visible/hidden/veryHidden(从“隐藏/取消隐藏”对话框中隐藏工作表)
  showWorksheet(isShow = false, sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (sheet) {
      if (typeof isShow == 'boolean') {
        sheet.state = isShow ? 'visible' : 'hidden'
      } else {
        sheet.state = isShow
      }
    }
  }

  // endregion

  // region 属性/样式

  // 修改工作表的标签背景色颜色
  tabColor(colorValue = 'ffff0000', sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    sheet.properties.tabColor = {argb: colorValue}
  }

  // 修改工作表属性
  setSheetProperties(propertiesObj, sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    sheet.properties = propertiesObj
  }

  // endregion

  // endregion


  // region 行/列操作相关

  // region 行列数

  // 获取内容的行数。中间文档行为空，则该行将包括在计数中
  getRowCount(sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.rowCount
  }

  // 获取非空的行数。 如果中间文档行为空，则该行将不包括在计数中
  getRowCountWithData(sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.actualRowCount
  }

  // 获取列数。中间文档行为空，则该行将包括在计数中
  getColumnCount(sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.columnCount
  }

  // 获取非空的列数。 如果中间文档列为空，则该列将不包括在计数中
  getColumnCountWithData(sheetObj) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.actualColumnCount
  }

  // endregion


  // region 行对象

  /* 属性
  * height: 行高。
  * hidden：隐藏行。true：隐藏，false：显示。默认为false。
  * number：行号。从1开始。
  * fill：填充。
  * font：字体。
  * border：边框。
  * alignment：对齐。
  * values：行的单元格列表值
  *  */

  // 获取单行。rowNum：行号，1开始
  getRow(rowNum = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.getRow(rowNum)
  }

  // 获取多行。包含起始值和结束值的行
  getRows(rowStart = 1, rowEnd = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    return sheet.getRows(rowStart, rowEnd - rowStart + 1)
  }

  // 在指定行上方插入行。value: {id: int,name: string, dob: dateTime}。
  // insertType: “ i”表示从上一行继承，“ i +”表示包含空单元格，“ o”表示原始样式，“ o +”表示包含空单元格，“ n”表示没有单元格
  insertRow(rowNum = undefined, value = {}, insertType = undefined, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (rowNum == undefined) {
      // 默认是最后一行插入
      rowNum = this.getRowCount() + 1
    }
    return sheet.insertRow(rowNum, value, insertType)
  }

  // 在指定行上方插入多行
  insertRows(rowNum = undefined, values = [], insertType = undefined, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (rowNum == undefined) {
      // 默认是最后一行插入
      rowNum = this.getRowCount() + 1
    }
    return sheet.insertRows(rowNum, values, insertType)
  }

  // 获取表格的全部非空行的对象。是否包含空行
  getAllRows(includeNothing = false, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (!includeNothing) {
      return sheet._rows.filter(row => row.values.length > 0)
    }
    return sheet._rows
  }

  // 删除单行
  delRow(rowNum, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisRow = this.getRow(rowNum, sheetObj)
    sheet.spliceRows(thisRow.number, 1)
  }

  // 删除多行。count是从第一个参数行(包含参数1)开始删除多少行。如果是指定到某一行就自己用delRow进行遍历
  delRows(rowStart = 1, count = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisRow = this.getRow(rowStart, sheetObj)
    for (let i = 0; i < count; i++) {
      sheet.spliceRows(thisRow.number + 0, 1)
    }
  }

  // 复制行。amount：往下复制多少行。insert：true为重复项插入新行；false将替换已有行
  copyRow(rowNum, amount = 1, insert = true, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisRow = this.getRow(rowNum, sheetObj)
    sheet.duplicateRow(thisRow.number, amount, insert)
  }

  // endregion


  // region 列对象

  /* 属性
  * width：列宽。
  * hidden：隐藏列。true：隐藏，false：显示。默认为false。
  * number：列号。从1开始。
  * fill：填充。
  * font：字体。
  * border：边框。
  * alignment：对齐。
  * values：列的单元格列表值
  * */

  // 获取单列。columnNum：列号，从1开始。或名称"A"
  getColumn(columnOrName = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (typeof columnOrName == 'string') {
      columnOrName = columnOrName.toUpperCase()
    }
    return sheet.getColumn(columnOrName)
  }

  // 获取多列。包含起始值和结束值的列
  getColumns(columnStart = 1, columnEnd = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    let result = []
    // 没有多列方法，需要自己进行遍历
    for (let i = columnStart; i <= columnEnd; i++) {
      result.push(sheet.getColumn(i))
    }
    return result;
  }

  // 获取全部列对象。是否包含空列
  getAllColumns(includeNothing = false, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (!includeNothing) {
      return sheet._columns.filter(item => item.values.length > 0)
    }
    return sheet._columns
  }

  // 插入一列。columnNum：列号，从1开始。或名称"A"。新数据会占用当前列，原列往右移
  insertColumn(columnNum = undefined, value = ['A1', 'A2'], sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisColumn = this.getColumn(columnNum, sheetObj)
    sheet.spliceColumns(thisColumn.number, 0, value)
  }

  // 插入多列。
  insertColumns(columnNum = undefined, values = [['A1', 'A2'], ['B1', 'B2']], sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisColumn = this.getColumn(columnNum, sheetObj)
    sheet.spliceColumns(thisColumn.number, 0, ...values)
  }

  // 删除列
  delColumn(columnNum, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisColumn = this.getColumn(columnNum, sheetObj)
    sheet.spliceColumns(thisColumn.number, 1)
  }

  // 删除多行列
  delColumns(columnStart = 1, count = 1, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    const thisColumn = this.getColumn(columnStart, sheetObj)
    for (let i = 0; i < count; i++) {
      sheet.spliceColumns(thisColumn.number + 0, 1)
    }
  }

  // 获取指定行的某一列。
  findColumnIndex(rowNum,callBack=undefined, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet

    // 列总数
    const columnNumber = this.getAllColumns(true)
    for (let i = 0; i < columnNumber?.length; i++) {
      const columnIndex = columnNumber[i].number
      // 获取该单元格
      const cell = this.getCell(rowNum, columnIndex, sheet)
      if (typeof callBack == 'function' && callBack(cell)) {
        return this.getColumn(columnIndex)
      }
    }
    return undefined
  }

  // endregion

  // endregion


  // region 单元格

  /* 单元格属性
  * address：单元格名称。
  * font：字体。
  * fill：填充。
  * border：边框。
  * row：行号，从1开始。
  * col：列号，从1开始，1即是A。
  * text：单元格文本(只读)。
  * value：单元格富文本，如果不是富文本等同于text。
  * */

  // 获取单元格。默认是A1
  getCell(rowNum = 1, columnOrSheet = undefined, sheetObj = undefined) {
    if (typeof rowNum == 'string') {
      // 根据单元格名称取
      let sheet = columnOrSheet ? columnOrSheet : this.nowSheet
      return sheet.getCell(rowNum.toUpperCase())
    } else {
      // 根据单元格行/列x序号取
      let sheet = sheetObj ? sheetObj : this.nowSheet
      return sheet.getCell(rowNum, columnOrSheet || 1)
    }
  }

  // 合并单元格。按开始行，开始列，结束行，结束列合并。如：2,2,3,4》B2:D3，单元格内容是B2的内容
  mergeByNumber(startRow, startColumn, endRow, endColumn, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    sheet.mergeCells(startRow, startColumn, endRow, endColumn)
  }

  // 合并单元格。按开始单元格，结束单元格合并。如：A2:D3》A2:D3，单元格内容是A2的内容
  mergeByName(startCell, endCell, sheetObj = undefined) {
    startCell = startCell.toUpperCase()
    endCell = endCell.toUpperCase()
    let sheet = sheetObj ? sheetObj : this.nowSheet
    sheet.mergeCells(startCell, endCell)
  }

  // 取消合并
  unMerge(cellName, sheetObj = undefined) {
    cellName = cellName.toUpperCase()
    let sheet = sheetObj ? sheetObj : this.nowSheet
    sheet.unMergeCells(cellName)
  }

  // endregion


  // region 视图views

  // region 冻结视图
  /* 冻结视图
  * rowCount：冻结多少行，要仅冻结行，请将其设置为 0 或 undefined。
  * columnCount：冻结多少列，同上。
  * topLeftCell：滚动条滚到哪个单元格（单元格会处于非冻结区域的左上角），例如："E1"。注意：不能是冻结单元格。默认为第一个未冻结的单元格
  * activeCell：当前活跃的活跃单元格，例如："D5"
  * */
  freeze(rowCount = undefined, columnCount = undefined, topLeftCell = undefined, activeCell = undefined, sheetObj = undefined) {
    let sheet = sheetObj ? sheetObj : this.nowSheet
    if (rowCount == undefined || columnCount == undefined){
      sheet.views = []
      return
    }
    if (topLeftCell){
      topLeftCell = topLeftCell.toUpperCase()
    }
    if (activeCell){
      activeCell = activeCell.toUpperCase()
    }
    sheet.views = [
      {state: 'frozen', xSplit: columnCount, ySplit: rowCount, topLeftCell: topLeftCell, activeCell: activeCell}
    ]
  }

  // endregion


  // endregion


  // region 保存/读取

  // 获取xlsx的文件流
  async getBuffer() {
    return await this.workbook.xlsx.writeBuffer()
  }

  // 保存到文件
  async save(name = 'xhyk', beforeFn = undefined, ExcelType = '') {
    const buffer = await this.getBuffer()
    const EXCEL_TYPE = ExcelType ? ExcelType : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
    const blob = new Blob([buffer], {type: EXCEL_TYPE});

    // 保存前回调验证。
    if (typeof beforeFn == 'function') {
      const isOk = await beforeFn(buffer, blob)
      if (isOk == false) {
        return;
      }
    }
    await saveAs(blob, `${name}.xlsx`)
    return blob
  }

  // 文件到buffer
  async fileToBuffer(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => {
        if (reader.error) {
          reject({status: false, message: reader.error});
        } else {
          resolve({status: true, data: new Uint8Array(reader.result)});
        }
      };
      reader.readAsArrayBuffer(file);
    });
  }

  // 读取文件的内容
  async read(file) {
    const $that = this

    async function _f() {
      return new Promise(async (resolve, reject) => {
        if (typeof file == 'string') {
          $that.workbook.xlsx.readFile(file)
              .then(res => {

                resolve({status: true, data: res, message: ''})
              })
              .catch(err => {
                resolve({status: false, message: err})
              })

        } else {
          const buffer = await $that.fileToBuffer(file)
          if (buffer.status == false) {
            resolve(buffer)
            return
          }

          $that.workbook.xlsx.load(buffer.data)
              .then(res => {
                resolve({status: true, data: res, message: ''})
              })
              .catch(err => {
                resolve({status: false, message: err})
              })
        }
      })
    }

    const result = await _f()
    if (result.status) {
      this.workSheetList = []
      this.workbook.eachSheet((worksheet, sheetId) => {
        this.workSheetList.push({
          sheetId: sheetId,
          sheetName: worksheet.name,
          sheet: worksheet
        })
        if (this.workSheetList.length > 0) {
          this.nowSheet = this.workSheetList[0].sheet
        }
      })
    }
    return result
  }


  // endregion


}